/*
 * 86Box    A hypervisor and IBM PC system emulator that specializes in
 *          running old operating systems and software designed for IBM
 *          PC systems and compatibles from 1981 through fairly recent
 *          system designs based on the PCI bus.
 *
 *          This file is part of the 86Box distribution.
 *
 *          Roland MPU-401 emulation.
 *
 *
 *
 * Authors: Sarah Walker, <https://pcem-emulator.co.uk/>
 *          DOSBox Team,
 *          Miran Grca, <mgrca8@gmail.com>
 *          TheCollector1995, <mariogplayer@gmail.com>
 *
 *          Copyright 2008-2020 Sarah Walker.
 *          Copyright 2008-2020 DOSBox Team.
 *          Copyright 2016-2020 Miran Grca.
 *          Copyright 2016-2020 TheCollector1995.
 */

#ifndef SOUND_MPU401_H
#define SOUND_MPU401_H

#define MPU401_VERSION      0x15
#define MPU401_REVISION     0x01
#define MPU401_QUEUE        1024
#define MPU401_INPUT_QUEUE  1024
#define MPU401_TIMECONSTANT (60000000 / 1000.0f)
#define MPU401_RESETBUSY    27.0f

/*helpers*/
#define M_GETKEY key[key / 32] & (1 << (key % 32))
#define M_SETKEY key[key / 32] |= (1 << (key % 32))
#define M_DELKEY key[key / 32] &= ~(1 << (key % 32))

typedef enum MpuMode {
    M_UART,
    M_INTELLIGENT
} MpuMode;

#define M_MCA 0x10

typedef enum MpuDataType {
    T_OVERFLOW,
    T_MARK,
    T_MIDI_SYS,
    T_MIDI_NORM,
    T_COMMAND
} MpuDataType;

typedef enum RecState {
    M_RECOFF,
    M_RECSTB,
    M_RECON
} RecState;

/* Messages sent to MPU-401 from host */
#define MSG_EOX      0xf7
#define MSG_OVERFLOW 0xf8
#define MSG_MARK     0xfc

/* Messages sent to host from MPU-401 */
#define MSG_MPU_OVERFLOW    0xf8
#define MSG_MPU_COMMAND_REQ 0xf9
#define MSG_MPU_END         0xfc
#define MSG_MPU_CLOCK       0xfd
#define MSG_MPU_ACK         0xfe

typedef struct mpu_t {
    uint16_t addr;
    int      uart_mode;
    int      intelligent;
    int      irq;
    int      midi_thru;
    int      queue_pos;
    int      queue_used;
    uint8_t  rx_data;
    uint8_t  is_mca;
    uint8_t  status;
    uint8_t  queue[MPU401_QUEUE];
    uint8_t  pos_regs[8];
    MpuMode  mode;
    uint8_t  rec_queue[MPU401_INPUT_QUEUE];
    int      rec_queue_pos;
    int      rec_queue_used;
    uint32_t ch_toref[16];
    struct track {
        int         counter;
        uint8_t     value[3];
        uint8_t     sys_val;
        uint8_t     vlength;
        uint8_t     length;
        MpuDataType type;
    } playbuf[8], condbuf;
    struct {
        int      conductor;
        int      cond_req;
        int      cond_set;
        int      block_ack;
        int      playing;
        int      reset;
        int      wsd;
        int      wsm;
        int      wsd_start;
        int      run_irq;
        int      irq_pending;
        int      track_req;
        int      send_now;
        int      eoi_scheduled;
        int      data_onoff;
        int      clock_to_host;
        int      sync_in;
        int      sysex_in_finished;
        int      rec_copy;
        RecState rec;
        uint8_t  tmask;
        uint8_t  cmask;
        uint8_t  amask;
        uint8_t  last_rtcmd;
        uint16_t midi_mask;
        uint16_t req_mask;
        uint32_t command_byte;
        uint32_t cmd_pending;
        uint32_t track;
        uint32_t old_track;
    } state;
    struct {
        uint8_t  timebase;
        uint8_t  old_timebase;
        uint8_t  tempo;
        uint8_t  old_tempo;
        uint8_t  tempo_rel;
        uint8_t  old_tempo_rel;
        uint8_t  tempo_grad;
        uint8_t  cth_rate[4];
        uint8_t  cth_mode;
        uint8_t  midimetro;
        uint8_t  metromeas;
        uint32_t cth_counter;
        uint32_t cth_old;
        uint32_t rec_counter;
        int32_t  measure_counter;
        int32_t  meas_old;
        int32_t  freq;
        int      ticks_in;
        int      active;
        float    freq_mod;
    } clock;
    struct {
        int      all_thru;
        int      midi_thru;
        int      sysex_thru;
        int      commonmsgs_thru;
        int      modemsgs_in;
        int      commonmsgs_in;
        int      bender_in;
        int      sysex_in;
        int      allnotesoff_out;
        int      rt_affection;
        int      rt_out;
        int      rt_in;
        int      timing_in_stop;
        int      data_in_stop;
        int      rec_measure_end;
        uint8_t  prchg_buf[16];
        uint16_t prchg_mask;
    } filter;
    struct {
        int      on;
        uint8_t  chan;
        uint8_t  trmask;
        uint32_t key[4];
    } chanref[5], inputref[16];
    pc_timer_t mpu401_event_callback;
    pc_timer_t mpu401_eoi_callback;
    pc_timer_t mpu401_reset_callback;
    void     (*ext_irq_update)(void *priv, int set);
    int      (*ext_irq_pending)(void *priv);
    void      *priv;
} mpu_t;

extern int mpu401_standalone_enable;
extern int mpu401_already_loaded;

extern const device_t mpu401_device;
extern const device_t mpu401_mca_device;

extern uint8_t MPU401_ReadData(mpu_t *mpu);
extern void    mpu401_write(uint16_t addr, uint8_t val, void *priv);
extern uint8_t mpu401_read(uint16_t addr, void *priv);
extern void    mpu401_setirq(mpu_t *mpu, int irq);
extern void    mpu401_change_addr(mpu_t *mpu, uint16_t addr);
extern void    mpu401_init(mpu_t *mpu, uint16_t addr, int irq, int mode, int receive_input);
extern void    mpu401_device_add(void);
extern void    mpu401_irq_attach(mpu_t *mpu, void (*ext_irq_update)(void *priv, int set), int (*ext_irq_pending)(void *priv), void *priv);

extern int  MPU401_InputSysex(void *priv, uint8_t *buffer, uint32_t len, int abort);
extern void MPU401_InputMsg(void *priv, uint8_t *msg, uint32_t len);

#endif /*SOUND_MPU401_H*/
